#include "openglwidget.h"
#include "..\src\gui\dialogs\qfiledialog.h"

OpenGLWidget::OpenGLWidget(QWidget *parent)
	: QGLWidget (QGLFormat(QGL::AlphaChannel | QGL::Rgba),parent),
	_width(700),
	_height(480),
	_scale(1),
	_x(0),
	_y(0),
	_z(50),
	pointToEdit(-1),
	subdivision_(10),
	typeOfLine_(OPEN_BSPLINE),
	isClosed(false),
	viewZ(false)
{
	points_ = new QList<Point3f>();
	subdivisionPoints_ = new QList<Point3f>();
}

OpenGLWidget::~OpenGLWidget()
{
	delete[] points_;
	delete[] subdivisionPoints_;
}

void OpenGLWidget::calculateCasteljau()
{
	if(points_->size() > 1)
	{
		for(double t = 0; t <= 1.001; t+=(1/(double)subdivision_))
		{
			Point3f toAdd = getCasteljauPoint(points_->length()-1,0,t);
			subdivisionPoints_->append(toAdd);
		}
	}
	//subdivisionPoints_->append()
}

Point3f OpenGLWidget::getCasteljauPoint( const int &r, const int &i, const double &t )
{
	if(r == 0) return (*points_)[i];

	Point3f p1 = getCasteljauPoint(r-1, i, t);
	Point3f p2 = getCasteljauPoint(r-1, i+1, t);

	return Point3f((1-t)*p1.x()+t*p2.x(),(1-t)*p1.y()+t*p2.y(),(1-t)*p1.z()+t*p2.z());
}

void OpenGLWidget::calculateQuadraticBesier()
{
	for(int p = 2; p < points_->length(); p+=2)
	
	{
		QList<Point3f> *intermediate = new QList<Point3f>;
		for(double t = 0 ; t <= 1.001; t+= 1.0/subdivision_)
		{
			Point3f A(((1.0f-t)*(*points_)[p-2])+(t*(*points_)[p-1]));
			Point3f B(((1.0f-t)*(*points_)[p-1])+(t*(*points_)[p]));

			Point3f P(((1-t)*A)+(t*B));

			subdivisionPoints_->append(P);
		}
	}
}

void OpenGLWidget::calculateCubicBesier()
{
	for(int p = 3; p < points_->length(); p+=3)
	{
		for(double t = 0 ; t <= 1.001; t+= 1/subdivision_)
		{
			Point3f A(((1.0f-t)*(*points_)[p-3])+(t*(*points_)[p-2]));
			Point3f B(((1.0f-t)*(*points_)[p-2])+(t*(*points_)[p-1]));
			Point3f C(((1.0f-t)*(*points_)[p-1])+(t*(*points_)[p]));
			
			Point3f M(((1-t)*A)+(t*B));
			Point3f N(((1-t)*B)+(t*C));

			Point3f P(((1-t)*M)+(t*N));
			

			//Point3f toAdd = pow((1-t),3)*(*points_)[p-3]+3*pow(1-t,2)*t*(*points_)[p-2]+3*(t-1)*t*t*(*points_)[p-1]+pow(t,3)*(*points_)[p];
			subdivisionPoints_->append(P);
		}
	}
}

void OpenGLWidget::calculateClosedBSpline()
{
	QList<Point3f> *thisIteration = new QList<Point3f>();;
	QList<Point3f> *previousIteration = points_;
	for(int i = 0; i < subdivision_ && i < 12; i++)
	{
		for(int j = 0; j < previousIteration->length(); j++)
		{
			if(j < previousIteration->length() - 1)
			{
				Point3f toAdd((3.0/4)*(*previousIteration)[j] + ((1.0/4)*(*previousIteration)[j+1]));
				thisIteration->append(toAdd);
				Point3f toAdd2((1.0/4)*(*previousIteration)[j] + ((3.0/4)*(*previousIteration)[j+1]));
				thisIteration->append(toAdd2);
			}
			else
			{
				Point3f toAdd((3.0/4)*(*previousIteration)[j] + ((1.0/4)*(*previousIteration)[0]));
				thisIteration->append(toAdd);
				Point3f toAdd2((1.0/4)*(*previousIteration)[j] + ((3.0/4)*(*previousIteration)[0]));
				thisIteration->append(toAdd2);
			}
				
		}
		//copy so we can subdivide again if necessary
		previousIteration = thisIteration;
		//empty
		thisIteration = new QList<Point3f>;
	}
	//delete[] thisIteration;
	//delete[] subdivisionPoints_;
	subdivisionPoints_ = previousIteration;
}

void OpenGLWidget::calculateOpenBspline()
{
	if(points_->size() < 2)
	{
		return;
	}
	QList<Point3f> *thisIteration = new QList<Point3f>;
	QList<Point3f> *previousIteration = points_;
	for(int i = 0; i < subdivision_ && i < 12; i++)
	{
		//beginning
		Point3f toAdd((*previousIteration)[0]);
		thisIteration->append(toAdd);
		Point3f toAdd2(1.0/2*(*previousIteration)[0] + 1.0/2*(*previousIteration)[1]);
		thisIteration->append(toAdd2);
		//everything else
		for(int j = 0; j < previousIteration->length()-1; j++)
		{
				Point3f toAdd5(3.0/4*(*previousIteration)[j] + 1.0/4*(*previousIteration)[j+1]);
				thisIteration->append(toAdd5);
				Point3f toAdd6(1.0/4*(*previousIteration)[j] + 3.0/4*(*previousIteration)[j+1]);
				thisIteration->append(toAdd6);
		}
		//end
		
		Point3f toAdd3(1.0/2*(*previousIteration)[previousIteration->length()-1] + 1.0/2*(*previousIteration)[previousIteration->length()-2]);
		thisIteration->append(toAdd3);
		Point3f toAdd4 = (*previousIteration)[previousIteration->length()-1];
		thisIteration->append(toAdd4);

		//copy so we can subdivide again if necessary
		previousIteration = thisIteration;
		//empty
		thisIteration = new QList<Point3f>;
	}
	subdivisionPoints_ = previousIteration;
}

void OpenGLWidget::calculateOpenCubicBspline()
{
	if(points_->size() < 5)
	{
		return;
	}

	QList<Point3f> *thisIteration = new QList<Point3f>;
	QList<Point3f> *previousIteration = points_;
	for(int i = 0; i < subdivision_ && i < 12; i++)
	{
		//beginning
		thisIteration->append((*previousIteration)[0]);
		thisIteration->append(1.0/2*(*previousIteration)[0]+1.0/2*(*previousIteration)[1]);
		thisIteration->append(3.0/4*(*previousIteration)[1]+1.0/4*(*previousIteration)[2]);
		thisIteration->append(3.0/16*(*previousIteration)[1]+11.0/16*(*previousIteration)[2]+1.0/8*(*previousIteration)[3]);

		//everything else
		for(int j = 3; j < previousIteration->length()-3; j++)
		{
			Point3f toAdd(1.0/2*(*previousIteration)[j-1] + 1.0/2*previousIteration->at(j));
			thisIteration->append(toAdd);
			Point3f toAdd2(1.0/8*(*previousIteration)[j-1] + 3.0/4*previousIteration->at(j) + 1.0/8*(*previousIteration)[j+1]);
			thisIteration->append(toAdd2);

		}
		//end
		thisIteration->append(3.0/16*(*previousIteration)[previousIteration->length()-2]+11.0/16*(*previousIteration)[previousIteration->length()-3]+1.0/8*(*previousIteration)[previousIteration->length()-4]);
		thisIteration->append(3.0/4*(*previousIteration)[previousIteration->length()-2]+1.0/4*(*previousIteration)[previousIteration->length()-3]);
		thisIteration->append(1.0/2*(*previousIteration)[previousIteration->length()-1]+1.0/2*(*previousIteration)[previousIteration->length()-2]);
		thisIteration->append((*previousIteration)[previousIteration->length()-1]);
		
		//copy so we can subdivide again if necessary
		previousIteration = thisIteration;
		//empty
		thisIteration = new QList<Point3f>;
	}
	subdivisionPoints_ = previousIteration;
}

void OpenGLWidget::calculateClosedCubicBSpline()
{
	if(points_->length() < 2)
	{
		return;
	}

	QList<Point3f> *thisIteration = new QList<Point3f>;
	QList<Point3f> *previousIteration = points_;
	for(int i = 0; i <= subdivision_&& i < 12; i++)
	{
		for(int j = 0; j < previousIteration->length(); j++)
		{
			if(j ==0)
			{
				Point3f toAdd(1.0/2*(*previousIteration)[previousIteration->length()-1] + 1.0/2*(*previousIteration)[j]);
				thisIteration->append(toAdd);
				Point3f toAdd2(1.0/8*(*previousIteration)[previousIteration->length()-1] + 3.0/4*(*previousIteration)[j] + 1.0/8*(*previousIteration)[j+1]);
				thisIteration->append(toAdd2);
			}
			else if(j < previousIteration->length() - 1)
			{
				Point3f toAdd(1.0/2*(*previousIteration)[j-1] + (1.0/2*(*previousIteration)[j]));
				thisIteration->append(toAdd);
				Point3f toAdd2(1.0/8*(*previousIteration)[j-1] + (3.0/4*(*previousIteration)[j]) + (1.0/8*(*previousIteration)[j+1]));
				thisIteration->append(toAdd2);
			}
			else
			{
				Point3f toAdd(1.0/2*(*previousIteration)[j-1] + 1.0/2*(*previousIteration)[j]);
				thisIteration->append(toAdd);
				Point3f toAdd2(1.0/8*(*previousIteration)[j-1] + 3.0/4*(*previousIteration)[j] + 1.0/8*(*previousIteration)[0]);
				thisIteration->append(toAdd2);
			}

		}
		//copy so we can subdivide again if necessary
		previousIteration = thisIteration;
		//empty
		thisIteration = new QList<Point3f>;
	}
	subdivisionPoints_ = previousIteration;
}

void OpenGLWidget::setLineType( lineType typeOfLine )
{
	this->typeOfLine_= typeOfLine;
	draw();
}

void OpenGLWidget::setSubDivisionCount( int count )
{
	this->subdivision_ = count;
	draw();
}

void OpenGLWidget::draw()
{
	subdivisionPoints_ = new QList<Point3f>;
	switch (typeOfLine_)
	{
	case CASTELJAU:
		calculateCasteljau();
		isClosed = false;
		break;
	case QUADRATIC_BESIER:
		calculateQuadraticBesier();
		isClosed = false;
		break;
	case CUBIC_BESIER:
		calculateCubicBesier();
		isClosed = false;
		break;
	case OPEN_BSPLINE:
		calculateOpenBspline();
		isClosed = false;
		break;
	case CLOSED_BSPLINE:
		calculateClosedBSpline();
		isClosed = true;
		break;
	case OPEN_CUBIC_BSPLINE:
		calculateOpenCubicBspline();
		isClosed = false;
		break;
	case CLOSED_CUBIC_BSPLINE:
		calculateClosedCubicBSpline();
		isClosed = true;
		break;
	}

	updateGL();
}

void OpenGLWidget::initializeGL() {
	glClearColor(1.0,1.0,1.0,1);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
}

void OpenGLWidget::resizeGL(int width, int height) {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	this->_width = width;
	this->_height = height;

	glViewport(0,0,width, height);

	float ratio;
	if (width > height) {
		ratio = (float)width/(float)height;
		_xRatio = ratio;
		_yRatio = 1;
		glOrtho(0, 100*ratio, 0, 100, -100, 100);
	} else {
		ratio = (float)height/(float)width;
		_xRatio = 1;
		_yRatio = ratio;
		glOrtho(0, 100, 0, 100*ratio, -100, 100);
	}

	glMatrixMode(GL_MODELVIEW);

	updateGL();
}

void OpenGLWidget::paintGL() {
	//GLUquadricObj *q = gluNewQuadric();

	glClear(GL_COLOR_BUFFER_BIT);	

	glLoadIdentity();

	glScaled(_scale,_scale,_scale);

	//Draw Points

	if(viewZ)
	{
		//glRotated(90,1.0,0.0,0.0);

		gluLookAt(0,50,100,0,-50,0,0,1,0);
	}
	

	drawPoints();

	//Draw curve

	drawCurve();

	glFlush();

}

void OpenGLWidget::drawPoints()
{
	if(!isClosed)
	{
		glBegin(GL_LINE_STRIP);
	}
	else
	{
		glBegin(GL_LINE_LOOP);
	}
	for(int i = 0; i < points_->size(); i++)
	{
		//draw in black.
		glColor4f(0.0f,0.0f,0.0f,1.0f);
		glVertex3f((*points_)[i].x(),(*points_)[i].y(), (*points_)[i].z());
	}
	glEnd();
}

void OpenGLWidget::drawCurve()
{
	if(!isClosed)
	{
		glBegin(GL_LINE_STRIP);
	}
	else
	{
		glBegin(GL_LINE_LOOP);
	}
	for(int i = 0; i < subdivisionPoints_->size(); i++)
	{
		//draw in blue
		glColor4f(0.0f,0.0f,1.0f,1.0f);
		glVertex3f((*subdivisionPoints_)[i].x(), (*subdivisionPoints_)[i].y(), (*subdivisionPoints_)[i].z());
	}
	glEnd();
}

void OpenGLWidget::setXY()
{
	//double x = ((_x-(_width/2.0))/_width)*2.0*_xRatio*(1/_scale)*50;
	_x = (100*_xRatio)/_width*_x;
	//double y = ((_y-(_height/2.0))/_height)*2.0*_yRatio*(1/_scale)*50;

	_y = -(100 -((100*_yRatio)/_height*_y));
}

void OpenGLWidget::mouseMoveEvent( QMouseEvent *event )
{
	_x = event->x();
	_y = event->y();
	if(viewZ == false)
	{
	setXY();

		if(pointToEdit >= 0)
		{
			(*points_)[pointToEdit].x(_x);
			(*points_)[pointToEdit].y(-_y);
			draw();
		}

	}

}

void OpenGLWidget::mouseReleaseEvent( QMouseEvent *event )
{
	_x = event->x();
	_y = event->y();

	setXY();

	if(viewZ == false)
	{
		if(event->button() == Qt::LeftButton)
		{
			//add a point

			points_->append(Point3f(_x,-_y,_z));
			draw();

		}

		if(event->button() == Qt::RightButton)
		{
			//find if a point matches the one I'm clicking on.
			int toRemove = findPoint(_x, -_y);

			if(toRemove >= 0)
			{
				points_->removeAt(toRemove);
				draw();
			}

		}

		if(event->button() == Qt::MiddleButton)
		{
			//stop editing
			pointToEdit = -1;
		}
	}

}

void OpenGLWidget::mousePressEvent( QMouseEvent *event )
{
	_x = event->x();
	_y = event->y();

	setXY();
	if(!viewZ)
	{
		if(event->button() == Qt::MiddleButton)
		{
			//start editing
			this->pointToEdit = findPoint(_x,-_y);
		}
	}

}

int OpenGLWidget::findPoint( double x, double y )
{
	int toReturn = -1;

	for(int i = 0; i < points_->size(); i++)
	{
		if(abs((*points_)[i].x() - x) < 2 && abs((*points_)[i].y() - y) < 2)
		{
			if(toReturn >= 0)
			{
				if((*points_)[toReturn].z() < (*points_)[i].z())
				{
					toReturn = i;
				}
			}
			else
			{
				toReturn = i;
			}
		}
	}
	return toReturn;
}

void OpenGLWidget::newCanvas()
{
	points_ = new QList<Point3f>;
	subdivisionPoints_ = new QList<Point3f>;

	updateGL();
}

void OpenGLWidget::save()
{
	if(fileSaved == false)
	{
		saveLocation.setFileName(QFileDialog::getSaveFileName(this,tr("Save File"),"",tr("Ass-1 Images (*.gal)")));
		if(saveLocation.open(QIODevice::WriteOnly))
		{
			QDataStream out(&saveLocation);
			out.setVersion(QDataStream::Qt_4_6);
			out <<*points_;
		}
		saveLocation.close();
		fileSaved = true;
	}
	else
	{
		if(saveLocation.open(QIODevice::WriteOnly))
		{
			QDataStream out(&saveLocation);
			out.setVersion(QDataStream::Qt_4_6);
			out <<*points_;
		}
		saveLocation.close();
	}
}

void OpenGLWidget::saveAs()
{
	saveLocation.setFileName(QFileDialog::getSaveFileName(this,tr("Save File"),"",tr("Ass-1 Images (*.gal)")));
	if(saveLocation.open(QIODevice::WriteOnly))
	{
		QDataStream out(&saveLocation);
		out.setVersion(QDataStream::Qt_4_6);
		out <<*points_;
	}
	saveLocation.close();
	fileSaved = true;
}

void OpenGLWidget::open()
{
	QFile saveLocation = QFileDialog::getOpenFileName(this,tr("Save File"),"",tr("Ass-1 Images (*.gal)"));

	this->saveLocation.setFileName(saveLocation.fileName());

	if(saveLocation.open(QIODevice::ReadOnly))
	{
		QDataStream in(&saveLocation);

		in.setVersion(QDataStream::Qt_4_6);
		in >> *points_;

		fileSaved = true;

		draw();
	}
}

void OpenGLWidget::setCasteljau()
{
	this->typeOfLine_ = CASTELJAU;
	draw();
}

void OpenGLWidget::setQuadratic_Besier()
{
	this->typeOfLine_ = QUADRATIC_BESIER;
	draw();
}

void OpenGLWidget::setCubic_Besier()
{
	this->typeOfLine_ = CUBIC_BESIER;
	draw();
}

void OpenGLWidget::setClosed_BSpline()
{
	this->typeOfLine_ = CLOSED_BSPLINE;
	draw();
}

void OpenGLWidget::setOpen_BSpline()
{
	this->typeOfLine_ = OPEN_BSPLINE;
	draw();
}

void OpenGLWidget::setClosed_Cubic_BSpline()
{
	this->typeOfLine_= CLOSED_CUBIC_BSPLINE;
	draw();
}

void OpenGLWidget::setOpen_Cubic_BSpline()
{
	this->typeOfLine_ = OPEN_CUBIC_BSPLINE;
	draw();
}

void OpenGLWidget::setZ( int toSet )
{
	_z = toSet;
	draw();
}

void OpenGLWidget::toggleViewZ()
{
	if(viewZ == true)
	{
		viewZ = false;
	}
	else
	{
		viewZ = true;
	}
	draw();
}


